/*
 *   UltraBench -- Connectivity Benchmarking Utility
 *
 *     net.hpp - Networking related classes
 *
 *
 *   Copyright (C) 2009 Ultratronik Entwicklungs GmbH 
 *
 *     - Author: Sören Wellhöfer <soeren.wellhoefer@gmx.net>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *   MA 02110-1301, USA.
 */

#ifndef NET_HPP
#define NET_HPP


#include "common.hpp"
#include "aux.hpp"

#include <QtWiAux>

#include <map>

extern "C" {
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
}

#include <cstring>
#include <cstdlib>


class TCPIPnetworking
{
	
public:
	enum ProcType {
		
		WINDOW_SCALE,
		TIME_STAMP,
		PMTU,
		SACK,
		MTU,
		SMAX,
		RMAX,
		
	};

	
private:
	
 	mutable std::map<ProcType, Proc> procmap;

	double rtt;
	
public:
	static const int max_mtu;

	TCPIPnetworking()
	{
		procmap[WINDOW_SCALE] = Proc("/proc/sys/net/ipv4/tcp_window_scaling");
		procmap[TIME_STAMP] = Proc("/proc/sys/net/ipv4/tcp_timestamps");
		procmap[PMTU] = Proc("/proc/sys/net/ipv4/ip_no_pmtu_disc");
		procmap[SACK] = Proc("/proc/sys/net/ipv4/tcp_sack");
		procmap[MTU] = Proc("/sys/class/net/eth0/mtu");
		procmap[RMAX] = Proc("/proc/sys/net/core/rmem_max");
		procmap[SMAX] = Proc("/proc/sys/net/core/wmem_max");
	}

	Proc& getProc(ProcType pt) const
	{
		return procmap[pt];
	}
	
public:

	static char* myIP()
	{
		char buf[256];
		struct hostent* ht;

		memset(buf, 0, sizeof buf);
		
		if((gethostname(buf, sizeof buf) == -1) ||
		   (ht = gethostbyname(buf)) == NULL) {

			perror("gethostbyname()");
			exit(-1);
		}
		
		return inet_ntoa(*(struct in_addr *)ht->h_addr);
	}	
	
};


//// Simple ICMP raw socket pinger thread ////

class Ping : public ErrorReporter, public IThread
{

private:
	
	// Connection
	char target_ip[20];
	char my_ip[20];
	int sockfd;


	// TCP and ICMP header
	char pack[sizeof(struct iphdr) + sizeof(struct icmphdr)];
	char buf[sizeof(struct iphdr) + sizeof(struct icmphdr)];

	struct iphdr* iphead;
	struct icmphdr* icmphead;
	
	struct sockaddr_in sai;

	
	// Measurment
	PrimitiveTimer timer;
	
	double rtt;
	int bytes_received;
	IMutexLock<double> rtt_lock;
	IMutexLock<int> bytes_received_lock;

public:

	static const short default_ttl = 64; /* hops */
	static const int ping_delay = (int)(0.5 * 1000000); /* microsec */
	
	Ping() : rtt(-1), rtt_lock(rtt), bytes_received_lock(bytes_received)
	{
		strcpy(my_ip, TCPIPnetworking::myIP());
	}

	void init(const char *ip)
	{
		strcpy(target_ip, ip);	
	}

	void setup(short ttl = default_ttl)
	{
		int optionval = 0;
		
		ErrorReporter::recover();
		
		// IP packet memory layout
		iphead = reinterpret_cast<struct iphdr*>(pack);
		icmphead = reinterpret_cast<struct icmphdr*>(iphead + sizeof(struct iphdr));
		

		// IP header
		iphead->ihl = 5;
		iphead->version = 4;
		iphead->tos = 0x8; /* High Troughput */
		iphead->tot_len = sizeof(struct iphdr) + sizeof(struct icmphdr);
		iphead->id = htons(0);
		iphead->frag_off = 0;
		iphead->ttl = ttl;
		iphead->protocol = IPPROTO_ICMP;
		iphead->saddr = inet_addr(my_ip);
		iphead->daddr = inet_addr(target_ip);
		iphead->check = calc_checksum((unsigned short *)iphead, sizeof(struct iphdr));

		// Create local raw socket
		if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) {
			
			error(ErrorReporter::RECOVERABLE, "Creating raw socket failed");
			return;
		};

		// Prevent kernel from supplying default IP packet header
		setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optionval, sizeof(int));


		// ICMP header
		icmphead->type = ICMP_ECHO;
		icmphead->code = 0;
		icmphead->un.echo.id = random();
		icmphead->un.echo.sequence = 0;
		icmphead->checksum = calc_checksum((unsigned short *)icmphead, sizeof(struct icmphdr));
		

		// Setup address structure of ping target
		sai.sin_family = AF_INET;
		sai.sin_addr.s_addr = inet_addr(target_ip);

#ifdef DEBUG
		std::cerr << "Ping setup done\n";
#endif
		
	}

	
	static unsigned short calc_checksum(unsigned short *addr, int len)
	{
		register int sum = 0;
		u_short answer = 0;
		register u_short *w = addr;
		register int nleft = len;

		
		while (nleft > 1) {
			sum += *w++;
			nleft -= 2;
		}
	
		if (nleft == 1) {
			*(u_char *) (&answer) = *(u_char *) w;
			sum += answer;
		}
	
		sum = (sum >> 16) + (sum & 0xffff);
		sum += (sum >> 16);
		answer = ~sum;
		return answer;
	}


	//
	// Do successive pings while thread is running.
	// Rtt will be measured and can be read using getRtt,
	// which is thread-safe.
	//
	void action(void *)
	{
		socklen_t addrlen = sizeof sai;

		struct timeval tv;
		int received;
		
		
		while(isRunning()) {

			ErrorReporter::recover();

			usleep(ping_delay);

			timer.timer(NULL);
			
			// Send ICMP request
			if((sendto(sockfd, pack, iphead->tot_len, 0, (struct sockaddr *)&sai, sizeof(struct sockaddr))) < 0) {
				
				ErrorReporter::error(ErrorReporter::RECOVERABLE, "Failed to ping. Retrying ...");
				rtt_lock.set(-1);
				continue;
			}

			// Wait for pong
			if((received = recvfrom(sockfd, buf, sizeof(struct iphdr) + sizeof(struct icmphdr), 0,
						(struct sockaddr *)&sai, &addrlen)) < 0) {

				ErrorReporter::error(ErrorReporter::RECOVERABLE, "Failed to receive pong. Retrying ...");
				rtt_lock.set(-1);
				continue;
			}

			timer.timer(&tv);

			// Calculate and save measured values
			double msecs = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;

			rtt_lock.set(msecs);
			bytes_received_lock.set(received);
		}
	}

	double getRtt() const
	{
		return rtt_lock.read();
	}

	int getBytesReceived() const
	{
		return bytes_received_lock.read();
	}


};


#endif
